<?php
/**
 * @file
 * Jsonrpc module.
 *
 * Reference material:
 * http://groups.google.com/group/json-rpc/web/json-rpc-2-0
 *
 *
 */

define('JSONRPC_PATH', isset($conf['jsonrpc_path']) ? $conf['jsonrpc_path'] : 'jsonrpc2');

if (!function_exists('__autoload')) { function __autoload($c) { jsonrpc_load_class($c); }}



function jsonrpc($method, $param = NULL) {
	if (is_array($method)) {
		$uri = $method[0];
		$method = $method[1];
		$request = array('jsonrpc' => '2.0', 'method' => $method, 'param' => $param, 'id' => 1);
		$context = stream_context_create(array('http' => array('method' => 'POST', 'content' => json_encode($request))));
		$out = file_get_contents($uri, FALSE, $context);
		$response = json_decode($out);
		if ($response === NULL) {
			echo "Error parsing json: $out";
			return;
		}
		return $response->result;
	}
}


/*
 *
 */
function jsonrpc_db_query() {
	$args = func_get_args();
	if (FALSE === ($rs = call_user_func_array('db_query', $args)))
		return FALSE;
		//throw new Exception('jsonrpc_db_query');
	$out = array();
	while ($row = db_fetch_object($rs))
		$out[] = $row;
	return $out;
}


/*
 *
 */
function jsonrpc_db_query_range() {
	$args = func_get_args();
	if (FALSE === ($rs = call_user_func_array('db_query_range', $args)))
		throw new Exception('jsonrpc_db_query_range');
	$out = array();
	while ($row = db_fetch_object($rs))
		$out[] = $row;
	return $out;
}


/*
 *
 */
function jsonrpc_indexed_db_query() {
	$args = func_get_args();
	if (!($rs = call_user_func_array('db_query', $args)))
		return FALSE;
	//$out = array();
	while ($row = db_fetch_object($rs)) {
		$out[] = $row;
		foreach ($row as $k => $v)break;
		$index[$v] = $row;
	}
	$out['#index'] =& $index;
	return $out;
}


/*
 *
 */
function jsonrpc_value_db_query() {
	$args = func_get_args();
	if (!($rs = call_user_func_array('db_query', $args)))
		return NULL;
	return db_result($rs);
}


/*
 *
 */
function jsonrpc_row_db_query() {
	$args = func_get_args();
	if (!($rs = call_user_func_array('db_query', $args)))
		return NULL;
	return db_fetch_object($rs);
}


/*
 *
 */
function jsonrpc_array_process_resultset($rs=FALSE) {
	if (!$rs) return FALSE;
	$out = array();
	if (!($row = db_fetch_array($rs)))
		return array('count' => 0, 'rows' => array(), 'fields' => array());
	$out['count'] = db_affected_rows();
	$out['columns'] = array_keys($row);
	$out['fields'] = array_flip(array_keys($row));
	$out['rows'][] = array_values($row);
	while ($row = db_fetch_array($rs))
		$out['rows'][] = array_values($row);
	return $out;
}


/*
 *
 */
function jsonrpc_assoc_db_query() {
	$rs = call_user_func_array('db_query', func_get_args());
	if (!$rs) return FALSE;
	while ($row = db_fetch_array($rs)) {
		//$r = array_values($row);
		$k = array_shift($row);
		$out[$k] = $row;
	}
	asort($out);
	return $out;
}


/*
 *
 */
function jsonrpc_md5_db_query() {
	$rs = call_user_func_array('db_query', func_get_args());
	if (!$rs) return FALSE;
	$out = array();
	while ($row = db_fetch_array($rs))
		$out[] = join(', ', $row);
	return md5(join('|', $out));
}


/*
 *
 */
function jsonrpc_array_db_query() {
	return jsonrpc_array_process_resultset(call_user_func_array('db_query', func_get_args()));
}


/*
 *
 */
function jsonrpc_array_db_query_range() {
	return jsonrpc_array_process_resultset(call_user_func_array('db_query_range', func_get_args()));
}


/* ******************************************************** */


/*
 *
 */
function jsonrpc_init_rc() {
	global $_jsonrpc_class_path, $_jsonrpc_initialized;
	if ($_jsonrpc_initialized) return;
	error_reporting(69);
	ini_set('display_errors', '1');
	ini_set('error_reporting', '69');
	ini_set('xdebug.extended_info', '0');
	ini_set('xdebug.default_enable', '0');
	ini_set('html_errors', '0');
	ini_set('default_socket_timeout', '5');
	ini_set('precision', '8');

	ini_set('zlib.output_compression', '1');
	ini_set('zlib.output_compression_level', '3');

	$_jsonrpc_initialized = TRUE;
}


/*
 *
 */
function jsonrpc_init($rc = FALSE) {
	global $_jsonrpc_initialized;
	if ($_jsonrpc_initialized) return;
	drupal_add_js(drupal_get_path('module', 'classes') . '/json.js', 'core');
	drupal_add_js(drupal_get_path('module', 'classes') . '/classes.js', 'core');
	if (variable_get('jsonrpc_include_xmlhttp', FALSE))
		drupal_set_html_head('<script type="text/javascript" src="'
			. base_path() . drupal_get_path('module', 'classes') . '/xmlhttp.js"></script>');
	$_jsonrpc_initialized = 1;
	require_once dirname(__FILE__) . '/../classes/Jsonrpc.class.php';
}


function jsonrpc_update2(&$classes = NULL) {
	static $lock_update = FALSE;
	if ($lock_update) return;
	$lock_update = TRUE;
	$classes = array();
	$dir = './classes';	
	if (is_dir($dir)) {
		foreach (glob("$dir/*.class.php", GLOB_NOSORT) as $f) {
			$c = strstr(basename($f), '.', TRUE);
			if (isset($classes[$c])) continue;
			$classes[$c] = $f;
		}
	}
	variable_del('jsonrpc_classes');
	variable_set('jsonrpc_classes', $classes);
	//watchdog('classes', 'classes updated');
}


/*
 *
 */
function jsonrpc_load_class($c) {
	static $classes = NULL;
	if (NULL === $classes) {
		$classes = variable_get('jsonrpc_classes', NULL);
		if (!$classes) {
			//watchdog('classes', "classes are not initialized; calling jsonrpc_update2()");
			jsonrpc_update2($classes);
		}
	}
	if (!isset($classes[$c])) {
		if ($user->uid != 1) return FALSE;
		//watchdog('classes', "classes: class '$c' not found; calling jsonrpc_update2()");
		jsonrpc_update2($classes);
	}
	if (!isset($classes[$c]) || !file_exists($classes[$c])) return FALSE;
//  echo 100;
	return include_once $classes[$c];
}


/*
 *
 */
function jsonrpc_shutdown_function() {
	global $_jsonrpc_output_buffer;

	while (ob_get_level() > 1)
		ob_end_flush();

	if ($x = trim($_jsonrpc_output_buffer)) {
		header('Content-Type: text/plain; charset=UTF-8');
		if (isset($_GET['format']) && $_GET['format'] == 'text') {
			echo $x;
			return;
		}
		$x = addcslashes($x, "\\\n\"");
		echo '{"classes":"2.0", "error":{"code":-32400, "message":"System error.", "data":"' . $x . '"}, "id":null}';
	}
}


/*
 *
 */
function jsonrpc_output_function($x) {
	global $_jsonrpc_output_buffer;
	if (is_resource(STDERR))
		fprintf(STDERR, $x);
	else
		$_jsonrpc_output_buffer .= $x;
}


/*
 *
 */
function _jsonrpc_set_header() {
	header('Content-Type: text/plain; charset=utf-8');
	//header('Expires: Tue, 03 Jul 2001 06:00:00 GMT');
	//header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
	header('Cache-Control: no-store, no-cache, max-age=0');
	header('Pragma: no-cache');
	//header('Connection: close');
}


/*
 *
 */
function jsonrpc_cache_sql($x = NULL) {
	static $y = NULL;
	if ($x === NULL) {
		$x = $y;
		$y = NULL;
		return $x;
	}
	$y = $x;
}



/*
 *
 */
function jsonrpc_caching_db_query($cache_sql, $sql /* , ... */) {
	$args = func_get_args();
	$cache_sql = array_shift($args);
	$cid = md5(serialize($args));
	$rs = db_query("SELECT key_sql, key_md5 FROM {cache} WHERE cid = '%s'", $cid);
	if (db_affected_rows()==1) {
		$row = db_fetch_object($rs);
		if ($cache_sql == $row->key_sql)
			$cache_key_md5 = jsonrpc_md5_db_query($row->key_sql);
		if ($cache_key_md5) {
			if ($cache_key_md5 == $row->key_md5) {
				$data = db_result(db_query("SELECT data FROM {cache} WHERE cid = '%s'", $cid));
				$data = gzuncompress($data);
				$data = unserialize($data);
				return $data;
			}
			db_query("UPDATE {cache} SET key_sql = NULL, key_md5 = NULL WHERE key_md5 = '%s'", $row->key_md5);
		}
		db_query("UPDATE {cache} SET data = NULL, key_sql = NULL, key_md5 = NULL WHERE cid = '%s'", $cid);
	}
	$result = call_user_func_array('jsonrpc_db_query', $args);
	if (FALSE === $result) return FALSE;
	$key_md5 = jsonrpc_md5_db_query($cache_sql);
	if (FALSE === $result) return FALSE;
	$data = serialize($result);
	$data = gzcompress($data, 2);
	db_query("UPDATE {cache} SET data = %b, key_sql = '%s', key_md5 = '%s' WHERE cid = '%s'", $data, $cache_sql, $key_md5, $cid);
	if (!db_affected_rows())
		db_query("INSERT INTO {cache} (cid, data, key_sql, key_md5) VALUES ('%s', %b, '%s', '%s')", $cid, $data, $cache_sql, $key_md5);
	return $result;
}


/*
 *
 */
function jsonrpc_boot($force = FALSE) {
	global $_jsonrpc_output_buffer, $user, $_jsonrpc_initialized;
	static $lock = FALSE;
	if ($lock) return;
	$lock = TRUE;
	if (!JSONRPC_PATH) return;
	$n = strlen(JSONRPC_PATH);

	if (JSONRPC_PATH !== substr($_GET['q'], 0, $n)) return;
	

	$q = trim(substr($_GET['q'], $n), '/');
	require_once dirname(__FILE__) . '/../classes/Jsonrpc.class.php';
	$_jsonrpc_initialized = FALSE;
	jsonrpc_init_rc();
	$_jsonrpc_output_buffer = '';
	register_shutdown_function('jsonrpc_shutdown_function');

	_jsonrpc_set_header();

	$classes = new Jsonrpc();
	$debug = $user->uid == 1 || isset($_GET['debug']);
	$f = fopen('php://input', 'r');
	$get = $_GET;
	$cnt = 0;

	while (!feof($f)) {

		$_GET = $get;
		$buf = array();
		while (!feof($f) && ($x = fgets($f)) && ($x != "\n") && ($x != "\r\n")) $buf[] = $x;
		$json = join('', $buf);
		unset($cid);
		jsonrpc_cache_sql();


		$cid = NULL;
		$r = json_decode($json);


		if (isset($r->uri)) $r->method .= '!' . preg_replace("/\bid=[^&]*/", '', $r->uri);
		//if (!(isset($r->cache) && !$r->cache))
		if (!isset($r->params))
			$r->params = NULL;
		$cid = md5($GLOBALS['user']->uid . '@' . $r->method . '/' . json_encode($r->params));

		$t0 = microtime(TRUE);

		if (!(isset($_GET['nocache']) || (isset($_GET['cache']) && !$_GET['cache']))) {
			$rs = db_query("SELECT key_sql, key_md5 FROM {cache} WHERE cid = '%s'", $cid);
			if ($rs && db_affected_rows()==1) {
				$row = db_fetch_object($rs);
				if ($cache_key_sql = $row->key_sql)
					$cache_key_md5 = jsonrpc_md5_db_query($cache_key_sql);
				if ($cache_key_md5) {
					if ($cache_key_md5 == $row->key_md5) {
						$data = db_result(db_query("SELECT data FROM {cache} WHERE cid='%s'", $cid));
						echo gzuncompress($data);
						echo "\n\n";
						continue;
					}
					db_query("UPDATE {cache} SET key_sql = NULL, key_md5 = NULL WHERE key_md5 = '%s'", $row->key_md5);
				}
			}
		} else {
			// clear cache if nocache param exists
			db_query("DELETE FROM {cache} WHERE cid = '%s'", $cid);
			unset($cid);
		}

		ob_start('jsonrpc_output_function');


		$t1 = microtime(TRUE);
		$x = $classes->run($r, $q);
		$t1 = 1000*(microtime(TRUE) - $t1);

		if (!headers_sent())
			_jsonrpc_set_header();

		ob_end_flush();
		$buf = trim($_jsonrpc_output_buffer);
		$_jsonrpc_output_buffer = '';

		if ($buf) {
			echo "output buffer:\n" . preg_replace("/(\r?\n) {2, }/", '\1', trim($buf)) . "\n";
			//exit;
		}

		$json = NULL;

		if ($cid && ($cache_sql = jsonrpc_cache_sql())) {
			$json = $classes->encode($x, FALSE);
			if ($cache_md5 = jsonrpc_md5_db_query($cache_sql)) {
				$data = gzcompress(preg_replace("/\n[ \t\r\n]*"."/", '', $json), 3);
				db_query("UPDATE {cache} SET data = %b, key_sql = '%s', key_md5 = '%s'
							WHERE cid = '%s'", $data, $cache_sql, $cache_md5, $cid);
				if (!db_affected_rows())
					db_query("INSERT INTO {cache} (cid, data, key_sql, key_md5)
							  VALUES ('%s', %b, '%s', '%s')", $cid, $data, $cache_sql, $cache_md5);
			}
		}
		if ($m = get_messages()) {
			$json = NULL;
			$x->messages = $m;
		}

		$t0 = 1000*(microtime(TRUE) - $t0);

		if ($json===NULL) {
			if ($debug)
				$x->debug = array(
					'time' => $t1,
					'time2' => $t0,
					'dt' => $t0 - $t1,
					'rand'  => rand(100000, 999999),
				);
			$json = $classes->encode($x, FALSE);
		}

		echo $json;
		echo "\n\n";
		flush();
	}
	fclose($f);
	$classes->__destruct();
	if (function_exists('session_save_session'))
		session_save_session(TRUE);
	exit;
}



